/* * Part of the CCNx Java Library. * * Copyright (C) 2008, 2009 Palo Alto Research Center, Inc. * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. You should have received * a copy of the GNU Lesser General Public License along with this library; * if not, write to the Free Software Foundation, Inc., 51 Franklin Street, * Fifth Floor, Boston, MA 02110-1301 USA. */ package org.ccnx.ccn.impl.security.crypto; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidParameterSpecException; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import org.ccnx.ccn.impl.support.DataUtils; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.io.content.ContentEncodingException; import org.ccnx.ccn.protocol.ContentName; import org.ccnx.ccn.protocol.PublisherPublicKeyDigest; /** * This is a very simple approach to encryption keying that uses a fixed, static * encryption key and an IV "seed" that is used to prefix segment-specific IVs * or counters. * * The segmenter will be called with parameters identifying: * * * the encryption algorithm and mode to use, if any * * the encryption key to use for this particular data item (the object to be segmented) * * an 8-byte value used as an IV seed for this item (CBC or other block mode) or a random counter * component (CTR) (derived in KeyDerivation) * * the desired full segment (packet) length, including supporting data * * The initial counter or IV for a given block B (number Bnum) in segment Snum will be constructed as follows: * * Block IV/CTR = IVseed || Snum || Bnum * * where the segment and block numbers is represented in unsigned, 1-based big endian format. * * The total width of the IV/Counter value is B, the block width of the cipher. For a stream * cipher (e.g. CTR mode), the width B is taken to be 16 bytes. * * This IV/CTR is divided into 3 components: * * Master IV/IVseed : the master IV seed value, specified by the caller. For this simple static key approach, * this is by default 8 bytes. It is given by masterIVLength(). * * Segment number: this is a binary representation of the segment number. A single-segment object * following the SegmentationProfile will still have a segment number component in its name, * and will use the specified segment number (usually SegmentationProfile.baseSegment()). * * The segment number is encoded in 1-based, unsigned, big-endian form, and * represented in the L-N rightmost bytes of the plaintext above, where L is the length of * the numeric representation of the segment number, and N is the length of the block number * within the segment (for CTR mode, this value is fixed at 1 for CBC and other block modes). * * The default width of the segment number is 6 bytes, leaving 8 bytes for the default Master IV * width. * * Block number: for CTR mode, the last 2 bytes of the IV contain the block (counter) index, * starting with 1. For CBC and other block modes, that last two bytes contains the * (big endian) value 0x0001. * * The same IV expansion function is used regardless of mode for simplicity. * * Many of the expansion function calculations are broken out into separate methods to * allow for easier subclassing. * * IMPORTANT NOTE: Do not use static keying to encrypt network objects in CTR mode, unless * you are careful to only save them once per key. Use CBC mode (under development) or * a dynamic keying method, such as KDFContentKeys. */ public class StaticContentKeys extends ContentKeys implements Cloneable { public static final int IV_MASTER_LENGTH = 8; // bytes public static final int SEGMENT_NUMBER_LENGTH = 6; // bytes private static final byte [] INITIAL_BLOCK_COUNTER_VALUE = new byte[]{0x00, 0x01}; public static final int BLOCK_COUNTER_LENGTH = INITIAL_BLOCK_COUNTER_VALUE.length; // bytes /** * StaticContentKeys constructor. * @param encryptionAlgorithm (e.g. AES/CTR/NoPadding) the encryption algorithm to use. * First component of algorithm should be the algorithm associated with the key. * @param key key material to be used * @param ivctr iv or counter material to be used with specified algorithm * @throws NoSuchPaddingException * @throws NoSuchAlgorithmException */ public StaticContentKeys(String encryptionAlgorithm, byte [] key, byte [] ivCtr) throws NoSuchAlgorithmException, NoSuchPaddingException { super(encryptionAlgorithm, key, ivCtr); } /** * Create a StaticContentKeys with the default algorithm. * @throws NoSuchPaddingException * @throws NoSuchAlgorithmException */ public StaticContentKeys(byte [] key, byte [] ivCtr) throws NoSuchAlgorithmException, NoSuchPaddingException { super(null, key, ivCtr); } /** * StaticContentKeys constructor. */ public StaticContentKeys(String encryptionAlgorithm, Key key, byte [] ivCtr) throws NoSuchAlgorithmException, NoSuchPaddingException { super(encryptionAlgorithm, key, ivCtr); } public StaticContentKeys(ContentKeys other) { super(other); } /** * Create a set of random encryption/decryption keys using the default algorithm. * @return a randomly-generated set of keys and IV that can be used for encryption * @throws NoSuchPaddingException * @throws NoSuchAlgorithmException */ public static synchronized ContentKeys generateRandomKeys() throws NoSuchAlgorithmException, NoSuchPaddingException { byte [] key = new byte[DEFAULT_KEY_LENGTH]; byte [] iv = new byte[IV_MASTER_LENGTH]; // do we want additional whitening? SecureRandom random = ContentKeys.getRandom(); random.nextBytes(key); random.nextBytes(iv); return new StaticContentKeys(key, iv); } public ContentKeys clone() { return new StaticContentKeys(this); } /** * Generate a segment encryption or decryption cipher using this stored * key material to encrypt or decrypt a particular segment. * * This will use the CCN defaults for IV handling, to ensure that segments * of a given larger piece of content do not have overlapping key streams. * Higher-level functionality embodied in the library (or application-specific * code) should be used to make sure that the key, _masterIV pair used for a * given multi-block piece of content is unique for that content. * * CCN encryption algorithms assume deterministic IV generation (e.g. from * cryptographic MAC or ciphers themselves), and therefore do not transport * the IV explicitly. Applications that wish to do so need to arrange * IV transport. * * We assume this stream starts on the first block of a multi-block segement, * so for CTR mode, the initial block counter is 1 (block == encryption * block). (Conventions for counter start them at 1, not 0.) The cipher * will automatically increment the counter; if it overflows the two bytes * we've given to it it will start to increment into the segment number. * This runs the risk of potentially using up some of the IV space of * other segments. * * CTR_init = IV_master || segment_number || block_counter * CBC_iv = E_Ko(IV_master || segment_number || 0x0001) * (just to make it easier, use the same feed value) * * CTR value is 16 bytes. * 8 bytes are the IV. * 6 bytes are the segment number. * last 2 bytes are the block number (for 16 byte blocks); if you * have more space, use it for the block counter. * IV value is the block width of the cipher. * * @param segmentNumber segment to encrypt/decrypt * @param encryption true for encryption, false for decryption * @return the Cipher * @throws InvalidKeyException * @throws InvalidAlgorithmParameterException * @throws ContentEncodingException * @see getSegmentEncryptionCipher(long) */ protected Cipher getSegmentCipher(ContentName contentName, PublisherPublicKeyDigest publisher, long segmentNumber, boolean encryption) throws InvalidKeyException, InvalidAlgorithmParameterException, ContentEncodingException { Cipher cipher = getCipher(); // Construct the IV/initial counter. if (0 == cipher.getBlockSize()) { Log.warning(_encryptionAlgorithm + " is not a block cipher!"); throw new InvalidAlgorithmParameterException(_encryptionAlgorithm + " is not a block cipher!"); } KeyAndIV keyAndIV = getKeyAndIVForContent(contentName, publisher, segmentNumber); if (keyAndIV.getIV().length < IV_MASTER_LENGTH) { throw new InvalidAlgorithmParameterException("Master IV length must be at least " + IV_MASTER_LENGTH + " bytes, it is: " + _masterKeyAndIVCtr.getIV().length); } IvParameterSpec iv_ctrSpec = buildIVCtr(keyAndIV, segmentNumber, cipher.getBlockSize()); AlgorithmParameters algorithmParams = null; try { algorithmParams = AlgorithmParameters.getInstance(getBaseAlgorithm()); algorithmParams.init(iv_ctrSpec); } catch (NoSuchAlgorithmException e) { Log.warning("Unexpected exception: have already validated that algorithm {0} exists: {1}", cipher.getAlgorithm(), e); throw new InvalidKeyException("Unexpected exception: have already validated that algorithm " + cipher.getAlgorithm() + " exists: " + e); } catch (InvalidParameterSpecException e) { Log.warning("InvalidParameterSpecException attempting to create algorithm parameters: {0}", e); throw new InvalidAlgorithmParameterException("Error creating a parameter object from IV/CTR spec!", e); } Log.finest(encryption?"En":"De"+"cryption Key: "+DataUtils.printHexBytes(keyAndIV.getKey().getEncoded())+" iv="+DataUtils.printHexBytes(iv_ctrSpec.getIV())); cipher.init(encryption ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, keyAndIV.getKey(), algorithmParams); return cipher; } protected KeyAndIV getKeyAndIVForContent(ContentName contentName, PublisherPublicKeyDigest publisher, long segmentNumber) throws InvalidKeyException, ContentEncodingException { return _masterKeyAndIVCtr; } /** * Turn a master IV and a segment number into an initial counter of IV for this segment * (used in CTR mode). * @param masterIV the master IV * @param segmentNumber the segment number * @param ctrLen the output IV length requested * @return the initial counter * @throws InvalidAlgorithmParameterException * @throws InvalidKeyException */ public IvParameterSpec buildIVCtr(KeyAndIV keyAndIV, long segmentNumber, int ctrLen) throws InvalidKeyException, InvalidAlgorithmParameterException { Log.finest("Thread="+Thread.currentThread()+" Building fixed IV/CTR - master="+DataUtils.printHexBytes(keyAndIV.getIV())+" segment="+segmentNumber+" ctrLen="+ctrLen); byte [] ctr = segmentSeedValue(keyAndIV.getIV(), segmentNumber, ctrLen); IvParameterSpec ctrSpec = new IvParameterSpec(ctr); Log.finest("CTR: ivParameterSpec source="+DataUtils.printHexBytes(ctr)+"ivParameterSpec.getIV()="+DataUtils.printHexBytes(keyAndIV.getIV())); return ctrSpec; } public static byte [] segmentSeedValue(byte [] ivCtr, long segmentNumber, int seedLen) { byte [] seed = new byte[seedLen]; System.arraycopy(ivCtr, 0, seed, 0, ivCtr.length); byte [] byteSegNum = ContentKeys.segmentNumberToByteArray(segmentNumber); System.arraycopy(byteSegNum, 0, seed, ivCtr.length, byteSegNum.length); System.arraycopy(INITIAL_BLOCK_COUNTER_VALUE, 0, seed, seed.length - BLOCK_COUNTER_LENGTH, BLOCK_COUNTER_LENGTH); return seed; } }